/** * First-party Briven Auth proxy (auth-core FDI). * * Browser → http://localhost:3000/api/auth/… * Upstream → https://api.briven.tech/v1/auth-core/fdi/… * * @briven/auth SDK builds: {apiOrigin}/v1/auth-core/fdi/signinup/code * With apiOrigin = same-origin + "/api/auth", full path is: * /api/auth/v1/auth-core/fdi/signinup/code * We strip a leading v1/auth-core/fdi/ so we never double the prefix. */ import { NextRequest, NextResponse } from "next/server"; import { brivenUpstreamOrigin, collectSetCookies, rewriteSetCookieForFirstParty, } from "@/lib/auth-proxy"; export const dynamic = "force-dynamic"; export const runtime = "nodejs"; function runtimeEnv(name: string): string { return (process.env[name] ?? "").trim(); } type RouteCtx = { params: Promise<{ path: string[] }> }; async function proxy(req: NextRequest, ctx: RouteCtx): Promise { const { path: segments } = await ctx.params; let path = (segments ?? []).join("/"); if (path.includes("..")) { return NextResponse.json({ ok: false, error: "invalid auth path" }, { status: 400 }); } // SDK may send full FDI prefix under /api/auth path = path.replace(/^v1\/auth-core\/fdi\/?/, ""); path = path.replace(/^v1\/auth-tenant\/?/, ""); path = path.replace(/^v1\/auth-core\/session\/me\/?$/, "session/me"); const incomingUrl = new URL(req.url); // session/me lives outside /fdi/* (gold path); get-session is legacy name const isSessionMe = path === "session/me" || path === "get-session"; const target = isSessionMe ? `${brivenUpstreamOrigin()}/v1/auth-core/session/me${incomingUrl.search}` : `${brivenUpstreamOrigin()}/v1/auth-core/fdi/${path}${incomingUrl.search}`; const headers = new Headers(); const pass = [ "content-type", "cookie", "authorization", "x-briven-project-id", "rid", "fdi-version", "st-auth-mode", "anti-csrf", "user-agent", "referer", "x-forwarded-for", "x-real-ip", "cf-connecting-ip", ] as const; for (const name of pass) { const v = req.headers.get(name); if (v) headers.set(name, v); } if (!headers.has("authorization")) { const pk = runtimeEnv("BRIVEN_AUTH_PUBLIC_KEY") || runtimeEnv("NEXT_PUBLIC_BRIVEN_AUTH_KEY"); if (pk.startsWith("pk_briven_auth_")) { headers.set("authorization", `Bearer ${pk}`); } } if (!headers.has("x-briven-project-id")) { const project = runtimeEnv("BRIVEN_PROJECT_ID") || runtimeEnv("NEXT_PUBLIC_BRIVEN_PROJECT_ID"); if (project.startsWith("p_")) { headers.set("x-briven-project-id", project); } } const clientIp = req.headers.get("cf-connecting-ip")?.trim() || req.headers.get("x-real-ip")?.trim() || req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || ""; if (clientIp) { headers.set("x-briven-client-ip", clientIp); if (!headers.has("x-real-ip")) headers.set("x-real-ip", clientIp); if (!headers.has("x-forwarded-for")) headers.set("x-forwarded-for", clientIp); } const origin = req.headers.get("origin") || incomingUrl.origin; if (origin) headers.set("origin", origin); // Ensure project id on query for engines that read it there const targetUrl = new URL(target); const project = headers.get("x-briven-project-id") || runtimeEnv("NEXT_PUBLIC_BRIVEN_PROJECT_ID"); if (project && !targetUrl.searchParams.has("briven_project_id")) { targetUrl.searchParams.set("briven_project_id", project); } const method = req.method.toUpperCase(); const hasBody = method !== "GET" && method !== "HEAD"; let upstream: Response; try { upstream = await fetch(targetUrl.toString(), { method, headers, body: hasBody ? await req.arrayBuffer() : undefined, redirect: "manual", }); } catch (err) { const message = err instanceof Error ? err.message : "upstream unreachable"; return NextResponse.json( { ok: false, code: "network_error", message: `Auth proxy could not reach Briven: ${message}`, }, { status: 502 }, ); } const outHeaders = new Headers(); for (const name of [ "content-type", "cache-control", "location", "x-request-id", "x-briven-session-handle", ]) { const v = upstream.headers.get(name); if (v) outHeaders.set(name, v); } for (const sc of collectSetCookies(upstream.headers)) { outHeaders.append("set-cookie", rewriteSetCookieForFirstParty(sc)); } return new Response(upstream.body, { status: upstream.status, statusText: upstream.statusText, headers: outHeaders, }); } export const GET = proxy; export const POST = proxy; export const PUT = proxy; export const PATCH = proxy; export const DELETE = proxy; export const HEAD = proxy; export const OPTIONS = proxy;